Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Merged by Bors] - Pointerfication followup: Type safety and cleanup #4621

Closed
wants to merge 12 commits into from

Conversation

james7132
Copy link
Member

Objective

The Ptr types gives free access to the underlying NonNull<u8>, which adds more publicly visible pointer wrangling than there needs to be. There are also a few edge cases where Ptr types could be more readily utilized for properly validating the soundness of ECS operations.

Solution

  • Replace *Ptr(Mut)::inner with cast which requires a concrete type to give the pointer. This function could also have a debug_assert with an alignment check to ensure that the pointer is aligned properly, but is currently not included.
  • Use OwningPtr::read in ECS macros over casting the inner pointer around.

@github-actions github-actions bot added the S-Needs-Triage This issue needs to be labelled label Apr 28, 2022
@james7132 james7132 added A-ECS Entities, components, systems, and events C-Code-Quality A section of code that is hard to understand or change and removed S-Needs-Triage This issue needs to be labelled labels Apr 28, 2022
@alice-i-cecile alice-i-cecile added the X-Controversial There is active debate or serious implications around merging this PR label Apr 28, 2022
@alice-i-cecile
Copy link
Member

alice-i-cecile commented Apr 28, 2022

Adding the Controversial tag due to the pointery trickery involved, to make sure we get Cart to review this before merging.

I don't expect much pushback on these changes though; they're very nice.

#[inline]
pub fn inner(&self) -> NonNull<u8> {
self.0
pub fn cast<T>(&self) -> NonNull<T> {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh this is dramatically nicer. I love this change.

Copy link
Member

@alice-i-cecile alice-i-cecile left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Quite straightforward. I really like the increased clarity and type safety we get here.

@BoxyUwU
Copy link
Member

BoxyUwU commented Apr 28, 2022

All the use sites you changed seem to immediately .as_ptr() and also all seem to be using T = u8 because of it being blobvec internals. I think we ought to change inner(&self) -> NonNull<u8> to as_ptr(self) -> *mut u8 instead. I like the changes to use .read though :)

@james7132
Copy link
Member Author

Main reason I chose cast was to effectively treat Ptr types like void*: you need to give it an underlying type to have any meaningful operation on it. The fact that BlobVec uses u8 internally is just an artifact of the interface it uses. The uses in Deref(Mut) and read likewise enforce this usage too. This is in the crate's public interface, and IMO we should try to smooth out the rough edges on anything we mark pub before someone starts relying on it.

Ideally, moving the pointer casts and conversions into Ptr types behind a carefully curated public interface also allows us to provide wider runtime safety checks in debug mode (as the comment in the OP above suggests) to ensure the lower level safety invariants don't get broken.

@BoxyUwU
Copy link
Member

BoxyUwU commented Apr 28, 2022

If you know the underlying type you should be calling deref/deref_mut/read and working with borrows instead. The only reason these types exist instead of using borrows is because we do not know the underlying type. There should never be a reason to do .cast::<RealType>.

Now that ptrification is merged we should add debug checks to those 3 methods, storing Option<TypeId> and a Layout inside of the Ptr* types when debug assertions are on.

Put another way- inner() exists solely for working with the data in an untyped fashion because if you know the type you should just call deref/deref_mut/read and work with those instead. Therefore its not a coincidence that all the callers of cast are using u8 its rather the point of the method.

@james7132
Copy link
Member Author

james7132 commented May 1, 2022

@BoxyUwU made the changes requested, ended up adding OwningPtr::drop_as and making to_ptr into pub(crate). This should at least disable using raw *mut u8 in userspace/non-ECS engine code and force use of the typed APIs.

With that said, is there a reason why OwningPtr does not have deref(_mut)? Ah nevermind, brainfart.

@TheRawMeatball
Copy link
Member

IMO we should revert to_ptr -> as_ptr, as to_* methods signify more active operations, e.g. to_string wheras as_* operations signify zero-cost casting like operations. Beyond this, we should also not mark these methods pub(crate) to make them useful for public abstractions, such as #4447.

Copy link
Member

@BoxyUwU BoxyUwU left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I do somewhat think we ought to rename the offset methods to byte_offset to make it clear at use sites whats going on but that could be done in a separate PR

/// associated lifetime.
#[inline]
#[allow(clippy::wrong_self_convention)]
pub unsafe fn as_ptr(self) -> *mut u8 {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this doesn't really need to be an unsafe fn it could be fn as_ptr(&self) -> *mut u8 to be maximally flexible but it probably doesn't matter

@@ -186,7 +186,7 @@ impl std::fmt::Debug for ComponentDescriptor {
impl ComponentDescriptor {
// SAFETY: The pointer points to a valid value of type `T` and it is safe to drop this value.
unsafe fn drop_ptr<T>(x: OwningPtr<'_>) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure we really need ComponentDescriptor::drop_ptr anymore considering you can just do OwningPtr::drop_as

Copy link
Member Author

@james7132 james7132 May 1, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I actually tried removing this and there was a lifetime mismatch. Using OwningPtr::drop_as::<T> doesn't satisfy for<'a> unsafe fn.... Is there a way to make this work?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oh... that makes sense I guess, oh well ._.

@BoxyUwU BoxyUwU added the S-Ready-For-Final-Review This PR has been approved by the community. It's ready for a maintainer to consider merging it label May 1, 2022
@alice-i-cecile
Copy link
Member

bors r+

bors bot pushed a commit that referenced this pull request May 2, 2022
# Objective
The `Ptr` types gives free access to the underlying `NonNull<u8>`, which adds more publicly visible pointer wrangling than there needs to be. There are also a few edge cases where Ptr types could be more readily utilized for properly validating the soundness of ECS operations.

## Solution
 - Replace `*Ptr(Mut)::inner` with `cast` which requires a concrete type to give the pointer. This function could also have a `debug_assert` with an alignment check to ensure that the pointer is aligned properly, but is currently not included.
 - Use `OwningPtr::read` in ECS macros over casting the inner pointer around.
@alice-i-cecile
Copy link
Member

bors r-

@bors
Copy link
Contributor

bors bot commented May 2, 2022

Canceled.

@cart
Copy link
Member

cart commented May 3, 2022

bors r+

bors bot pushed a commit that referenced this pull request May 3, 2022
# Objective
The `Ptr` types gives free access to the underlying `NonNull<u8>`, which adds more publicly visible pointer wrangling than there needs to be. There are also a few edge cases where Ptr types could be more readily utilized for properly validating the soundness of ECS operations.

## Solution
 - Replace `*Ptr(Mut)::inner` with `cast` which requires a concrete type to give the pointer. This function could also have a `debug_assert` with an alignment check to ensure that the pointer is aligned properly, but is currently not included.
 - Use `OwningPtr::read` in ECS macros over casting the inner pointer around.
@bors bors bot changed the title Pointerfication followup: Type safety and cleanup [Merged by Bors] - Pointerfication followup: Type safety and cleanup May 3, 2022
@bors bors bot closed this May 3, 2022
exjam pushed a commit to exjam/bevy that referenced this pull request May 22, 2022
# Objective
The `Ptr` types gives free access to the underlying `NonNull<u8>`, which adds more publicly visible pointer wrangling than there needs to be. There are also a few edge cases where Ptr types could be more readily utilized for properly validating the soundness of ECS operations.

## Solution
 - Replace `*Ptr(Mut)::inner` with `cast` which requires a concrete type to give the pointer. This function could also have a `debug_assert` with an alignment check to ensure that the pointer is aligned properly, but is currently not included.
 - Use `OwningPtr::read` in ECS macros over casting the inner pointer around.
@james7132 james7132 deleted the ptrfication-followup branch June 22, 2022 08:24
ItsDoot pushed a commit to ItsDoot/bevy that referenced this pull request Feb 1, 2023
# Objective
The `Ptr` types gives free access to the underlying `NonNull<u8>`, which adds more publicly visible pointer wrangling than there needs to be. There are also a few edge cases where Ptr types could be more readily utilized for properly validating the soundness of ECS operations.

## Solution
 - Replace `*Ptr(Mut)::inner` with `cast` which requires a concrete type to give the pointer. This function could also have a `debug_assert` with an alignment check to ensure that the pointer is aligned properly, but is currently not included.
 - Use `OwningPtr::read` in ECS macros over casting the inner pointer around.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-ECS Entities, components, systems, and events C-Code-Quality A section of code that is hard to understand or change S-Ready-For-Final-Review This PR has been approved by the community. It's ready for a maintainer to consider merging it X-Controversial There is active debate or serious implications around merging this PR
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants